﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Felbot.Interface.Users;
using Felbot.IO;

namespace Felbot.Interaction {
    /// <summary>
    /// This static class allows you to manage users.
    /// </summary>
    public static class UserEntryControl {
        private static XmlControl<XmlUserRoot> m_XmlControl;

        private static List<XmlEntry> m_EntryList;

        /// <summary>
        /// A list of the entry objects currently loaded.
        /// </summary>
        public static List<XmlEntry> EntryList {
            get {
                return m_EntryList;
            }
        }

        /// <summary>
        /// Initializes the users. Loads them from the file.
        /// </summary>
        public static void Initialize() {
            m_XmlControl = new XmlControl<XmlUserRoot>(Program.ApplicationFolder + "\\users.fbx");
            m_EntryList = m_XmlControl.Document.Users;
        }

        /// <summary>Saves to the Users.fbx file.</summary>
        public static void Save() {
            m_XmlControl.Document.Users = m_EntryList;
            m_XmlControl.Save();
        }

        /// <summary>
        /// Reloads the Users.fbx.
        /// </summary>
        public static void Reload() {
            Initialize();
        }

        /// <summary>
        /// Gets an entry by entry name.
        /// </summary>
        /// <param name="name">The entry name.</param>
        /// <returns>An XmlEntry object containing entry information.</returns>
        public static XmlEntry GetEntry(string name) {
            foreach (XmlEntry entry in m_EntryList)
                if (entry.Name == name)
                    return entry;
            return null;
        }

        /// <summary>
        /// Gets a list of groups which contain the specified entry.
        /// </summary>
        /// <param name="entry">The entry.</param>
        /// <returns>A list of entry names which contain the specified entry.</returns>
        public static List<string> GetGroupsContaining(XmlEntry entry) {
            List<string> groups = new List<string>();
            foreach (XmlEntry group in m_EntryList) {
                if (group.EntryType == XmlEntryType.Group && group.Members.Contains(entry.Name)) {
                    groups.Add(group.Name);
                }
            }
            return groups;
        }

        /// <summary>
        /// Sets the specified entry to have the specified groups contain it.
        /// </summary>
        /// <param name="entry">The entry.</param>
        /// <param name="m_Groups">The groups.</param>
        public static void SetGroupsContaining(XmlEntry entry, List<string> groups) {
            foreach (XmlEntry group in m_EntryList) {
                if (group.EntryType == XmlEntryType.Group) {
                    foreach (string groupName in groups) {
                        if (group.Name == groupName && !group.Members.Contains(entry.Name))
                            group.Members.Add(entry.Name);
                    }
                }
            }
        }

        /// <summary>Adds a new entry edit information object to the entry.</summary>
        /// <param name="entry">The entry that was edited.</param>
        /// <param name="user">The user who performed the edit.</param>
        /// <param name="now">The date and time the edit was performed.</param>
        public static void SetEditNow(XmlEntry entry, string user, DateTime now) {
            XmlEntryEdit edit = new XmlEntryEdit(user, now);
            entry.Edits.Add(edit);
        }

        /// <summary>
        /// Updates all references to entry.
        /// </summary>
        /// <param name="oldName">The old name of the entry.</param>
        /// <param name="newName">The new name of the entry.</param>
        public static void UpdateName(string oldName, string newName) {
            foreach (XmlEntry entry in m_EntryList) {
                if (entry.EntryType == XmlEntryType.Group)
                    for (int i = 0; i < entry.Members.Count; i++)
                        if (entry.Members[i] == oldName)
                            entry.Members[i] = newName;
            }
        }

        /// <summary>
        /// Gets a string representing a free "NewEntry#" entry to make a new user entry by the name of.
        /// </summary>
        /// <returns>Returns a string representing a number.</returns>
        public static string GetMakeName {
            get {
                int l = 1;
                while (GetEntry("NewEntry" + l.ToString()) != null) {
                    l++;
                }
                return "NewEntry" + l.ToString();
            }
        }

        /// <summary>
        /// Creates a new entry by the specified name which was created by the "User Entry Manager".
        /// </summary>
        /// <param name="name">The name of the entry.</param>
        /// <returns>The entry created.</returns>
        public static XmlEntry MakeEntry(string name) {
            XmlEntry entry = new XmlEntry();
            entry.Name = name;
            entry.EntryType = XmlEntryType.User;
            entry.Has = new XmlEntryHas();
            entry.Has.Flags = new List<string>();
            entry.Members = new List<string>();
            entry.Notes = new List<string>();
            entry.Created = new XmlEntryEdit("User Entry Manager", DateTime.Now);
            m_EntryList.Add(entry);
            Save();
            return entry;
        }

        /// <summary>
        /// Creates a new entry by the specified name with the specified type, flags, and creator.
        /// </summary>
        /// <param name="name">The name of the entry.</param>
        /// <returns>The entry created.</returns>
        public static XmlEntry MakeEntry(string name, XmlEntryType type, string[] flags, string by) {
            XmlEntry entry = new XmlEntry();
            entry.Name = name;
            entry.EntryType = type;
            entry.Has = new XmlEntryHas();
            entry.Has.Flags = new List<string>();
            foreach (string flag in flags)
                entry.Has.Flags.Add(flag);
            entry.Members = new List<string>();
            entry.Notes = new List<string>();
            entry.Created = new XmlEntryEdit(by, DateTime.Now);
            m_EntryList.Add(entry);
            Save();
            return entry;
        }

        /// <summary>
        /// Creates a new entry by the specified name with the specified type, flags, owner group, and creator.
        /// </summary>
        /// <param name="name">The name of the entry.</param>
        /// <returns>The entry created.</returns>
        public static XmlEntry MakeEntry(string name, XmlEntryType type, string[] flags, string owner, string by) {
            XmlEntry entry = new XmlEntry();
            entry.Name = name;
            entry.EntryType = type;
            entry.Has = new XmlEntryHas();
            entry.Has.Flags = new List<string>();
            foreach (string flag in flags)
                entry.Has.Flags.Add(flag);
            entry.Members = new List<string>();
            entry.Notes = new List<string>();
            entry.Created = new XmlEntryEdit(by, DateTime.Now);
            m_EntryList.Add(entry);
            List<string> containing = new List<string>();
            containing.Add(owner);
            SetGroupsContaining(entry, containing);
            Save();
            return entry;
        }

        /// <summary>
        /// Creates a new entry by the specified name with the specified type, flags, owner groups, and creator.
        /// </summary>
        /// <param name="name">The name of the entry.</param>
        /// <returns>The entry created.</returns>
        public static XmlEntry MakeEntry(string name, XmlEntryType type, string[] flags, string[] owners, string by) {
            XmlEntry entry = new XmlEntry();
            entry.Name = name;
            entry.EntryType = type;
            entry.Has = new XmlEntryHas();
            entry.Has.Flags = new List<string>();
            foreach (string flag in flags)
                entry.Has.Flags.Add(flag);
            entry.Members = new List<string>();
            entry.Notes = new List<string>();
            entry.Created = new XmlEntryEdit(by, DateTime.Now);
            m_EntryList.Add(entry);
            List<string> containing = new List<string>();
            foreach (string owner in owners)
                containing.Add(owner);
            SetGroupsContaining(entry, containing);
            Save();
            return entry;
        }

        /// <summary>
        /// Removes the specified entry from the list.
        /// </summary>
        /// <param name="entry">The entry object.</param>
        public static void RemoveEntry(XmlEntry entry) {
            m_EntryList.Remove(entry);
            Save();
        }

        public static XmlEntry GetCumulativeEntry(ChannelUser user) {
            string name = user.RealUsername;
            List<XmlEntry> entryMatches = new List<XmlEntry>();
            foreach (XmlEntry entry in m_EntryList) {
                switch (entry.EntryType) {
                    case XmlEntryType.Group:
                    case XmlEntryType.Phrase:
                        break;
                    default:
                    case XmlEntryType.User:
                        if (entry.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase)) {
                            entryMatches.Add(entry);
                        } else if (entry.Name.Contains("*")) {
                            if (Regex.Match(
                                            name,
                                            "^" + Regex.Escape(entry.Name).Replace("\\*", ".*").Replace("\\?", ".") + "$",
                                            RegexOptions.IgnoreCase | RegexOptions.Singleline).Success)
                                entryMatches.Add(entry);
                        } else if (entry.Name.StartsWith("regex::")) {
                            if (Regex.Match(
                                            name,
                                            entry.Name.Substring(7),
                                            RegexOptions.IgnoreCase | RegexOptions.Singleline).Success)
                                entryMatches.Add(entry);
                        }
                        break;
                    case XmlEntryType.Client:
                        switch (entry.Name.ToLower()) {
                            default:
                                if (user.StatString.ClientCode == entry.Name.ToUpper())
                                    entryMatches.Add(entry);
                                break;
                            case "all":
                                entryMatches.Add(entry);
                                break;
                            case "sc":
                                if (user.StatString.ClientCode == "STAR" ||
                                    user.StatString.ClientCode == "SEXP" ||
                                    user.StatString.ClientCode == "JSTR" ||
                                    user.StatString.ClientCode == "SSHR")
                                    entryMatches.Add(entry);
                                break;
                            case "d1":
                                if (user.StatString.ClientCode == "DRTL" ||
                                    user.StatString.ClientCode == "DSHR")
                                    entryMatches.Add(entry);
                                break;
                            case "d2":
                                if (user.StatString.ClientCode == "D2DV" ||
                                    user.StatString.ClientCode == "D2XP")
                                    entryMatches.Add(entry);
                                break;
                            case "wc2":
                                if (user.StatString.ClientCode == "W2BN")
                                    entryMatches.Add(entry);
                                break;
                            case "wc3":
                                if (user.StatString.ClientCode == "WAR3" ||
                                    user.StatString.ClientCode == "W3XP")
                                    entryMatches.Add(entry);
                                break;
                        }
                        break;
                    case XmlEntryType.GameStat:
                        break;
                    case XmlEntryType.Wc3Clan:
                        if (user.StatString.ClanTag.Equals(entry.Name, StringComparison.CurrentCultureIgnoreCase))
                            entryMatches.Add(entry);
                        break;
                    case XmlEntryType.Wc3ClanRank:
                        break;
                }
            }

            if (entryMatches.Count == 0)
                return null;

            List<XmlEntry> accumulated = new List<XmlEntry>();
            List<string> containers = new List<string>();
            List<string> tmpcontainers = new List<string>();
            List<string> groups = new List<string>();
            foreach (XmlEntry entry in entryMatches)
                containers.Add(entry.Name);
            do {
                foreach (string entryName in containers) {
                    XmlEntry entry = GetEntry(entryName);
                    if (entry != null && !accumulated.Contains(entry))
                        accumulated.Add(entry);
                }
                tmpcontainers = containers;
                containers.Clear();
                foreach (string entryName in tmpcontainers) {
                    XmlEntry entry = GetEntry(entryName);
                    groups = GetGroupsContaining(entry);
                    foreach (string group in groups)
                        if (group != null && !accumulated.Contains(GetEntry(group)) && !tmpcontainers.Contains(group))
                            containers.Add(group);
                }
            }
            while (containers.Count > 0);

            if (accumulated.Count == 0)
                return null;

            XmlEntry cumulativeEntry = new XmlEntry();
            cumulativeEntry.Name = user.RealUsername + " [" + GetStringFromList(entryMatches) + "]";
            cumulativeEntry.EntryType = XmlEntryType.Cumulative;
            cumulativeEntry.Has = new XmlEntryHas();
            cumulativeEntry.Has.Flags = new List<string>();
            foreach (XmlEntry entry in accumulated)
                foreach (string flag in entry.Has.Flags)
                    if (!cumulativeEntry.Has.Flags.Contains(flag))
                        cumulativeEntry.Has.Flags.Add(flag);
            cumulativeEntry.Members = new List<string>();
            cumulativeEntry.Notes = new List<string>();
            cumulativeEntry.Created = new XmlEntryEdit();
            cumulativeEntry.Created.Time = DateTime.Now;
            cumulativeEntry.Created.By = "Cumulative Entry";
            cumulativeEntry.Edits = new List<XmlEntryEdit>();
            return cumulativeEntry;
        }

        private static string GetStringFromList(List<XmlEntry> list) {
            string l = "";
            foreach (XmlEntry ll in list)
                l += ll.Name + ", ";
            if (l != "")
                l = l.Substring(0, l.Length - 2);
            return l;
        }
    }
}
